/*
  Copyright (c) 1987,1997, Prentice Hall
  All rights reserved.
  
  Redistribution and use of the MINIX operating system in source and
  binary forms, with or without modification, are permitted provided
  that the following conditions are met:
  
     * Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.
  
     * Redistributions in binary form must reproduce the above
       copyright notice, this list of conditions and the following
       disclaimer in the documentation and/or other materials provided
       with the distribution.
  
     * Neither the name of Prentice Hall nor the names of the software
       authors or contributors may be used to endorse or promote
       products derived from this software without specific prior
       written permission.
  
  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS, AUTHORS, AND
  CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  IN NO EVENT SHALL PRENTICE HALL OR ANY AUTHORS OR CONTRIBUTORS BE
  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
  OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* tail - copy the end of a file	Author: Norbert Schlenker */

/*   Syntax:	tail [-f] [-c number | -n number] [file]
 *		tail -[number][c|l][f] [file]		(obsolescent)
 *		tail +[number][c|l][f] [file]		(obsolescent)
 *   Flags:
 *	-c number	Measure starting point in bytes.  If number begins
 *			with '+', the starting point is relative to the
 *			the file's beginning.  If number begins with '-'
 *			or has no sign, the starting point is relative to
 *			the end of the file.
 *	-f		Keep trying to read after EOF on files and FIFOs.
 *	-n number	Measure starting point in lines.  The number
 *			following the flag has significance similar to
 *			that described for the -c flag.
 *
 *   If neither -c nor -n are specified, the default is tail -n 10.
 *
 *   In the obsolescent syntax, an argument with a 'c' following the
 *   (optional) number is equivalent to "-c number" in the standard
 *   syntax, with number including the leading sign ('+' or '-') of the
 *   argument.  An argument with 'l' following the number is equivalent
 *   to "-n number" in the standard syntax.  If the number is not
 *   specified, 10 is used as the default.  If neither 'c' nor 'l' are
 *   specified, 'l' is assumed.  The character 'f' may be suffixed to
 *   the argument and is equivalent to specifying "-f" in the standard
 *   syntax.  Look for lines marked "OBSOLESCENT".
 *
 *   If no file is specified, standard input is assumed. 
 *
 *   P1003.2 does not specify tail's behavior when a count of 0 is given.
 *   It also does not specify clearly whether the first byte (line) of a
 *   file should be numbered 0 or 1.  Historical behavior is that the
 *   first byte is actually number 1 (contrary to all Unix standards).
 *   Historically, a count of 0 (or -0) results in no output whatsoever,
 *   while a count of +0 results in the entire file being copied (just like
 *   +1).  The implementor does not agree with these behaviors, but has
 *   copied them slavishly.  Look for lines marked "HISTORICAL".
 *   
 *   Author:    Norbert Schlenker
 *   Copyright: None.  Released to the public domain.
 *   Reference: P1003.2 section 4.59 (draft 10)
 *   Notes:	Under Minix, this program requires chmem =30000.
 *   Bugs:	No internationalization support; all messages are in English.
 */

/* Force visible Posix names */
#ifndef _POSIX_SOURCE
#define _POSIX_SOURCE 1
#endif

/* External interfaces */
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>

/* External interfaces that should have been standardized into <getopt.h> */
extern char *optarg;
extern int optind;

/* We expect this constant to be defined in <limits.h> in a Posix program,
 * but we'll specify it here just in case it's been left out.
 */
#ifndef LINE_MAX
#define LINE_MAX 2048		/* minimum acceptable lower bound */
#endif

/* Magic numbers suggested or required by Posix specification */
#define SUCCESS	0		/* exit code in case of success */
#define FAILURE 1		/*                   or failure */
#define DEFAULT_COUNT 10	/* default number of lines or bytes */
#define MIN_BUFSIZE (LINE_MAX * DEFAULT_COUNT)
#define SLEEP_INTERVAL	1	/* sleep for one second intervals with -f */

#define FALSE 0
#define TRUE 1

/* Internal functions - prototyped under Minix */
int tail(int count, int bytes, int read_until_killed);
int keep_reading(void);
void usage(void);

int main(int argc, char *argv[])
{
  int cflag = FALSE;
  int nflag = FALSE;
  int fflag = FALSE;
  int number = -DEFAULT_COUNT;
  char *suffix;
  int opt;
  struct stat stat_buf;

/* Determining whether this invocation is via the standard syntax or
 * via an obsolescent one is a nasty kludge.  Here it is, but there is
 * no pretense at elegance.
 */
  if (argc == 1) {		/* simple:  default read of a pipe */
	exit(tail(-DEFAULT_COUNT, 0, fflag));
  }
  if ((argv[1][0] == '+') ||	/* OBSOLESCENT */
      (argv[1][0] == '-' && ((isdigit(argv[1][1])) ||
			     (argv[1][1] == 'l') ||
			     (argv[1][1] == 'c' && argv[1][2] == 'f')))) {
	--argc; ++argv;
	if (isdigit(argv[0][1])) {
		number = (int)strtol(argv[0], &suffix, 10);
		if (number == 0) {		/* HISTORICAL */
			if (argv[0][0] == '+')
				number = 1;
			else
				exit(SUCCESS);
		}
	} else {
		number = (argv[0][0] == '+') ? DEFAULT_COUNT : -DEFAULT_COUNT;
		suffix = &(argv[0][1]);
	}
	if (*suffix != '\0') {
		if (*suffix == 'c') {
			cflag = TRUE;
			++suffix;
		}
		else
		if (*suffix == 'l') {
			nflag = TRUE;
			++suffix;
		}
	}
	if (*suffix != '\0') {
		if (*suffix == 'f') {
			fflag = TRUE;
			++suffix;
		}
	}
	if (*suffix != '\0') {	/* bad form: assume to be a file name */
		number = -DEFAULT_COUNT;
		cflag = nflag = FALSE;
		fflag = FALSE;
	} else {
		--argc; ++argv;
	}
  } else {			/* new standard syntax */
	while ((opt = getopt(argc, argv, "c:fn:")) != EOF) {
		switch (opt) {
		      case 'c':
			cflag = TRUE;
			if (*optarg == '+' || *optarg == '-')
				number = atoi(optarg);
			else
			if (isdigit(*optarg))
				number = -atoi(optarg);
			else
				usage();
			if (number == 0) {		/* HISTORICAL */
				if (*optarg == '+')
					number = 1;
				else
					exit(SUCCESS);
			}
			break;
		      case 'f':
			fflag = TRUE;
			break;
		      case 'n':
			nflag = TRUE;
			if (*optarg == '+' || *optarg == '-')
				number = atoi(optarg);
			else
			if (isdigit(*optarg))
				number = -atoi(optarg);
			else
				usage();
			if (number == 0) {		/* HISTORICAL */
				if (*optarg == '+')
					number = 1;
				else
					exit(SUCCESS);
			}
			break;
		      default:
			usage();
			/* NOTREACHED */
		}
	}
	argc -= optind;
	argv += optind;
  }

  if (argc > 1 ||		/* too many arguments */
      (cflag && nflag)) {	/* both bytes and lines specified */
	usage();
  }

  if (argc > 0) {		/* an actual file */
	if (freopen(argv[0], "r", stdin) != stdin) {
		fputs("tail: could not open ", stderr);
		fputs(argv[0], stderr);
		fputs("\n", stderr);
		exit(FAILURE);
	}
	/* There is an optimization possibility here.  If a file is being
	 * read, we need not look at the front of it.  If we seek backwards
         * from the end, we can (potentially) avoid looking at most of the
	 * file.  Some systems fail when asked to seek backwards to a point
	 * before the start of the file, so we avoid that possibility.
	 */
	if (number < 0 && fstat(fileno(stdin), &stat_buf) == 0) {
		long offset = cflag ? (long)number : (long)number * LINE_MAX;

		if (-offset < stat_buf.st_size)
			fseek(stdin, offset, SEEK_END);
	}
  } else {
	fflag = FALSE;		/* force -f off when reading a pipe */
  }
  exit(tail(number, cflag, fflag));
  /* NOTREACHED */
}

int tail(int count, int bytes, int read_until_killed)
/* count;			   lines or bytes desired */
/* bytes;			   TRUE if we want bytes */
/* read_until_killed;		   keep reading at EOF */
{
  int c;
  char *buf;			/* pointer to input buffer */
  char *buf_end;		/* and one past its end */
  char *start;			/* pointer to first desired character in buf */
  char *finish;			/* pointer past last desired character */
  int wrapped_once = FALSE;	/* TRUE after buf has been filled once */

/* This is magic.  If count is positive, it means start at the count'th
 * line or byte, with the first line or byte considered number 1.  Thus,
 * we want to SKIP one less line or byte than the number specified.  In
 * the negative case, we look backward from the end of the file for the
 * (count + 1)'th newline or byte, so we really want the count to be one
 * LARGER than was specified (in absolute value).  In either case, the
 * right thing to do is:
 */
  --count;

/* Count is positive:  skip the desired lines or bytes and then copy. */
  if (count >= 0) {
	while (count > 0 && (c = getchar()) != EOF) {
		if (bytes || c == '\n')
			--count;
	}
	while ((c = getchar()) != EOF) {
		if (putchar(c) == EOF)
			return FAILURE;
	}
	if (read_until_killed)
		return keep_reading();
	return ferror(stdin) ? FAILURE : SUCCESS;
  }

/* Count is negative:  allocate a reasonably large buffer. */
  if ((buf = (char *)malloc(MIN_BUFSIZE + 1)) == (char *)NULL) {
	fputs("tail: out of memory\n", stderr);
	return FAILURE;
  }
  buf_end = buf + (MIN_BUFSIZE + 1);

/* Read the entire file into the buffer. */
  finish = buf;
  while ((c = getchar()) != EOF) {
	*finish++ = c;
	if (finish == buf_end) {
		finish = buf;
		wrapped_once = TRUE;
	}
  }
  if (ferror(stdin))
	return FAILURE;

/* Back up inside the buffer.  The count has already been adjusted to
 * back up exactly one character too far, so we will bump the buffer
 * pointer once after we're done.
 * 
 * BUG: For large line counts, the buffer may not be large enough to
 *	hold all the lines.  The specification allows the program to
 *	fail in such a case - this program will simply dump the entire
 *	buffer's contents as its best attempt at the desired behavior.
 */
  if (finish != buf || wrapped_once) {		/* file was not empty */
	start = (finish == buf) ? buf_end - 1 : finish - 1;
	while (start != finish) {
		if ((bytes || *start == '\n') && ++count == 0)
			break;
		if (start == buf) {
			start = buf_end - 1;
			if (!wrapped_once)	/* never wrapped: stop now */
				break;
		} else {
			--start;
		}
	}
	if (++start == buf_end) {		/* bump after going too far */
		start = buf;
	}
	if (finish > start) {
		fwrite(start, 1, finish - start, stdout);
	} else {
		fwrite(start, 1, buf_end - start, stdout);
		fwrite(buf, 1, finish - buf, stdout);
	}
  }
  if (read_until_killed)
	return keep_reading();
  return ferror(stdout) ? FAILURE : SUCCESS;
}

/* Wake at intervals to reread standard input.  Copy anything read to
 * standard output and then go to sleep again.
 */
int keep_reading(void)
{
  static char buf[1024];
  int n;
  int i;
  off_t pos;
  struct stat st;

  pos = lseek(0, (off_t) 0, SEEK_CUR);
  for (;;) {
  	for (i = 0; i < 60; i++) {
  		while ((n = read(0, buf, sizeof(buf))) > 0) {
  			if (write(1, buf, n) < 0) return FAILURE;
  		}
  		if (n < 0) return FAILURE;

		sleep(SLEEP_INTERVAL);
	}

	/* Rewind if suddenly truncated. */
	if (pos != -1) {
		if (fstat(0, &st) == -1) {
			pos = -1;
		} else
		if (st.st_size < pos) {
			pos = lseek(0, (off_t) 0, SEEK_SET);
		} else {
			pos = st.st_size;
		}
	}
  }
}

/* Tell the user the standard syntax. */
void usage(void)
{
  fputs("Usage: tail [-f] [-c number | -n number] [file]\n", stderr);
  exit(FAILURE);
}
